/*
 *    This file is part of CeluMap.
 *
 *    CeluMap is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    CeluMap is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    Copyright 2006, 2007, 2008 Gonzalo Merayo
 *    gonzalo (at) celumap.com.ar
 *    www.celumap.com.ar
 *
 */

package mapWriters;

import figures.Figure;
import geometry.Rectangle;

import index.NameCoder;
import indexWriters.NameIndexWriter;

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;


import map.file.FileConstants;
import map.file.FileMap;

public class IndexedMapWriter implements MapWriter {
	private static final int max_figuras_por_mapa = 350;
	private static final int min_figuras_por_mapa = 150;
	private static final int hojas_por_nodo = 16;
	List<Figure> figs = new LinkedList<Figure>();
	//Rectangle area = null;
	
	public void addFigure(Figure f)
	{
		figs.add(f);
	/*	if(area == null)
			area = new Rectangle(f.getBoundingBox());
		else
			area.unite(f.getBoundingBox());
			*/
	};
	
	public Collection<LinkedList<Figure>> split(int nsplits){
		double[] xc = new double[nsplits];
		double[] yc = new double[nsplits];
		int[] count = new int[nsplits];
		int[] xf = new int[figs.size()];
		int[] yf = new int[figs.size()];
		initialize(xc, yc, xf, yf);
		int seeds = 1;
		System.out.println("splitting "+figs.size()+" figures");
		boolean terminar = false;
		do{
			seeds = insertSeeds(seeds, xc, yc, nsplits);
			iterate(seeds, xf, yf, xc, yc);
			for(int i=0;i<seeds;i++)
				count[i]=0;
			for(Figure fig:figs){
				//System.out.println(fig.xcenter()+"|"+fig.ycenter()+"|"+fig);
				int i = findClosest(fig.getBoundingBox().xcenter(), fig.getBoundingBox().ycenter(), xc, yc, seeds);
				count[i]++;
			}
			for(int i=0;i<seeds;i++)
				if(count[i]<min_figuras_por_mapa){
					terminar = true;
					break;
				}
			System.out.println(seeds);
		}while(seeds != nsplits && !terminar);
		ArrayList<LinkedList<Figure>> res = new ArrayList<LinkedList<Figure>>();
		for(int i=0;i<nsplits;i++)
			res.add(new LinkedList<Figure>());
		for(Figure fig:figs){
			int i = findClosest(fig.getBoundingBox().xcenter(), fig.getBoundingBox().ycenter(), xc, yc, nsplits);
			res.get(i).add(fig);
		}
		
		return res;
	}

	private int insertSeeds(int i, double[] xc, double[] yc, int nsplits) {
		for(int j=0;j<i;j++){
			if((i+j)<nsplits){
				xc[j+i]=xc[j]+1;
				yc[j+i]=yc[j]+1;
				xc[j]-=1;
				yc[j]-=1;
			}
		}
		return Math.min(2*i, nsplits);
	}

	private void initialize(double[] xc, double[] yc, int[] xf, int[] yf) {
		int j = 0;
		for(Figure fig:figs){
			xc[0] += (xf[j] = fig.getBoundingBox().xcenter());
			yc[0] += (yf[j++] = fig.getBoundingBox().ycenter());
		}
		xc[0] /= figs.size();
		yc[0] /= figs.size();

	}

	private void iterate(int nsplits, int[] xf, int[] yf, double[] xc, double[] yc) {
		boolean changes = true;
		double[] sumx = new double[nsplits];
		double[] sumy = new double[nsplits];
		int[] nc = new int[nsplits];
		while(changes){
			changes = false;
			for(int i=0;i<nsplits;i++)
				sumx[i] = sumy[i] = nc[i] = 0;
			for(int i=0;i<xf.length;i++){
				int n = findClosest(xf[i], yf[i], xc, yc, nsplits);
				sumx[n] += xf[i];
				sumy[n] += yf[i];
				nc[n]++;
			}
			for(int i=0;i<nsplits;i++){
				if(nc[i]==0)
					continue;
				int xx = (int) (sumx[i]/nc[i]);
				int yy = (int) (sumy[i]/nc[i]);
				if(xc[i] != xx || yc[i]!=yy){
					changes = true;
					//System.out.println(i+" "+Math.sqrt((xc[i]-xx)*(xc[i]-xx)+(yc[i]-yy)*(yc[i]-yy)));
				}
				xc[i] = xx;
				yc[i] = yy;
			}
		}
	}
	
	private int findClosest(int xf, int yf, double[] xc, double[] yc, int splits) {
		double min = java.lang.Double.POSITIVE_INFINITY;
		int n = 0;
		for(int i=0;i<splits;i++){
			double dist = (xf-xc[i])*(xf-xc[i])+(yf-yc[i])*(yf-yc[i]);
			if(dist < min){
				min = dist;
				n = i;
			}
		}
		return n;
	}

	public void write(DataOutputStream os) throws IOException
	{
		Collection<LinkedList<Figure>> lf =  split(hojas_por_nodo);
		os.writeShort(lf.size());
		for(LinkedList<Figure> f:lf)
		{
			List<String> soundex = new LinkedList<String>();
			if(f.size()==0)
				continue;
			MapWriter w;
			if(f.size()<max_figuras_por_mapa){
				w = new BaseMapWriter();
				os.writeByte(FileConstants.FILE_TYPE_MAP);
			}else{
				w = new IndexedMapWriter();
				os.writeByte(FileConstants.FILE_TYPE_INDEX);
			}
			Rectangle b = null;
			String s = FileNameSorter.sort();
			for(Figure fig:f){
				if(w instanceof BaseMapWriter && fig.getName()!=null){
					String sss =new String(fig.getName());
					for(String word:sss.split(" "))
						if(!NameCoder.codeWord(word).equals(""))
							soundex.add(NameCoder.codeWord(word));
				}
				w.addFigure(fig);
				//System.out.println(fig.getBoundingBox()); 
				if(b == null)
					b = new Rectangle(fig.getBoundingBox());
				else
					b.unite(fig.getBoundingBox());
			}
			
			FileMap.writeString(os, s);
			b.unload(os);
			//System.out.println(b);
			DataOutputStream d = new DataOutputStream(new FileOutputStream(s));
			w.write(d);
			d.close();
			
			NameIndexWriter.getInstance().index(s, soundex);
		}
	}
}